﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using XamlInspector.Foundation;
using XamlInspector.Model.Resources;

namespace XamlInspector.Model
{
    public class XamlElement : ViewModelBase, IEquatable<XamlElement>
    {
        private readonly XamlElement _parent;
        private Dictionary<FrameworkElement, XamlElement> _children;
        private ObservableCollection<XamlProperty> _properties;
        private ObservableCollection<XamlResource> _resources;
        private bool _hitTestOverride;
        private bool _isHitTestVisible;


        public XamlElement(FrameworkElement element, XamlElement parent = null, int nestingLevel = 0)
        {
            _parent = parent;
            Element = element;
            Type = element.GetType();
            NestingLevel = nestingLevel;
            Name = element.Name;
        }

        public List<XamlElement> Children
        {
            get
            {
                Refresh();
                return _children.Values.ToList();
            }
        }

        public XamlElement Parent
        {
            get { return _parent; }
        }

        public IEnumerable<XamlElement> AllChildren
        {
            get
            {
                yield return this;

                foreach (var child in Children)
                {
                    foreach (var subChild in child.AllChildren)
                    {
                        yield return subChild;
                    }
                }
            }
        }

        public bool HasChildren { get { return _children.Any(); } }

        public ObservableCollection<XamlProperty> Properties
        {
            get
            {
                if (_properties == null)
                {
                    _properties = new ObservableCollection<XamlProperty>(PropertyHelper.GetProperties(Element));
                }

                return _properties;
            }
        }

        public IEnumerable<IGrouping<string, XamlProperty>> PropertyGroups
        {
            get
            {
                return Properties.GroupBy(p => p.Category);
            }
        }

        public ObservableCollection<XamlResource> Resources
        {
            get
            {
                if (_resources == null)
                {
                    _resources = new ObservableCollection<XamlResource>();
                    LoadResources();
                }

                return _resources;
            }
        }

        private async void LoadResources()
        {
            var resources = await ResourceHelper.GetResources(Element);
            resources.ForEach(_resources.Add);
        }

        public string Name { get; set; }
        public FrameworkElement Element { get; private set; }
        public Type Type { get; set; }
        public int NestingLevel { get; private set; }

        public string Text
        {
            get
            {
                return string.IsNullOrEmpty(Name)
                    ? string.Format("<{0}>", Type.Name)
                    : string.Format("<{0} x:Name=\"{1}\">", Type.Name, Name);
            }
        }

        public int TotalCount
        {
            get
            {
                return AllChildren.Count();
            }
        }

        public void Refresh()
        {
            if (_children == null)
            {
                _children = new Dictionary<FrameworkElement, XamlElement>();
            }

            var childElements = new List<FrameworkElement>();
            var frame = Element as Frame;
            if (frame != null)
            {
                var frameContent = frame.Content as FrameworkElement;
                childElements.Add(frameContent);
            }
            else
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Element); i++)
                {
                    childElements.Add((FrameworkElement)VisualTreeHelper.GetChild(Element, i));
                }
            }

            var elementsToRemove = _children.Keys.Except(childElements).ToList();
            elementsToRemove.ForEach(e => _children.Remove(e));

            var elementsToAdd = childElements.Except(_children.Keys).ToList();
            elementsToAdd.ForEach(e => _children.Add(e, new XamlElement(e, this, NestingLevel + 1)));

            //Notify("TotalCount");
            //Notify("HasChildren");
        }

        public bool Equals(XamlElement other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Equals(Element, other.Element);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((XamlElement)obj);
        }

        public bool HitTestOverride
        {
            get { return _hitTestOverride; }
            set
            {
                _hitTestOverride = value;
                if (value)
                {
                    _isHitTestVisible = Element.IsHitTestVisible;
                    Element.IsHitTestVisible = true;
                }
                else
                {
                    Element.IsHitTestVisible = _isHitTestVisible;
                }
            }
        }

        public override int GetHashCode()
        {
            return (Element != null ? Element.GetHashCode() : 0);
        }

        public static bool operator ==(XamlElement left, XamlElement right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(XamlElement left, XamlElement right)
        {
            return !Equals(left, right);
        }

        public override string ToString()
        {
            return Text;
        }
    }
}
